﻿namespace Bundler
{
    using nQuant;
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.IO;
    using System.Linq;
    using System.Text.RegularExpressions;
    using System.Web;

    public class CssMinifySpriteEmbed : CssMinify
    {
        #region Constants and Fields

        private const string Format = "\n\r\n\r.{0} {{ background: url(data:image/png;base64,{1}) no-repeat 0 0; }}";
        private static readonly Regex Url = new Regex(@"url\(['""]?(?<url>[^)]+?\?embed)['""]?\)", RegexOptions.Singleline);
        private List<SpritePart> spriteParts;
        private int _currentWidth;

        #endregion

        #region Public Methods and Operators

        public override void Process(BundleResponse response)
        {
            if(response == null)
                throw new ArgumentNullException("response");

            spriteParts = new List<SpritePart>();
            response.RequestAction = () => SetHeaders(response.Bundle.Options.EnableOptimizations, spriteParts.Select(x => x.FileInfo.FullName).ToArray());

            BundleResponseHelper.GetContent(response, true);

            var rootPath = HttpHelper.MapPath("~/");
            if (String.IsNullOrWhiteSpace(rootPath))
                return;

            // remove block comments - jQuery UI CSS likes has { and } in block comments which would throw us off
            response.Content = Regex.Replace(response.Content, "/\\*.*?\\*/", string.Empty, RegexOptions.Compiled | RegexOptions.Singleline);

            foreach (Match match in Url.Matches(response.Content))
            {
                if (match.Groups.Count < 2)
                    continue;

                var relativePath = match.Groups[1].Value;
                if (string.IsNullOrEmpty(relativePath))
                    continue;

                relativePath = relativePath.Replace("?embed", string.Empty);
                var absolutePath = Path.Combine(rootPath, relativePath);
                var fileName = Path.GetFullPath((new Uri(absolutePath)).LocalPath);

                var file = new FileInfo(fileName);

                var spritePart = spriteParts.FirstOrDefault(x => x.FileInfo.FullName == file.FullName);
                if (spritePart == null)
                {
                    if (!file.Exists)
                        continue;

                    spritePart = new SpritePart { FileInfo = file };

                    using (var img = Image.FromFile(file.FullName))
                    {
                        spritePart.Width = img.Width;
                        spritePart.Height = img.Height;
                        spritePart.StartsAt = _currentWidth;
                    }
                    _currentWidth += spritePart.Width;

                    var lastStartBrace = response.Content.LastIndexOf("{", match.Index, StringComparison.Ordinal);
                    var lastEndBrace = response.Content.LastIndexOf("}", match.Index, StringComparison.Ordinal);
                    spritePart.IdentifierSource = response.Content.Substring(lastEndBrace + 1, lastStartBrace - lastEndBrace - 1);

                    spriteParts.Add(spritePart);
                }
            }

            if (!spriteParts.Any())
                return;

            using (var ms = new MemoryStream())
            {
                using (var combinedImage = Combine(spriteParts.Select(x => x.FileInfo.FullName).ToArray()))
                {
                    var quantizer = new WuQuantizer();
                    using (var bmp = new Bitmap(combinedImage))
                    {
                        using (var quantized = quantizer.QuantizeImage(bmp))
                        {
                            quantized.Save(ms, ImageFormat.Png);
                        }
                    }
                }

                response.Content += String.Format(Format, response.Bundle.Options.Css.SpriteClassName, Convert.ToBase64String(ms.ToArray()));
            }

            foreach (var spritePart in spriteParts)
            {
                var ids = new List<string>();
                var id = spritePart.IdentifierSource;
                if (id.Contains(","))
                {
                    ids.AddRange(id.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries));
                }
                else
                {
                    ids.Add(id);
                }
                var outIds = new List<string>();
                foreach (var thisId in ids)
                {
                    var newId = thisId.Trim() + "." + response.Bundle.Options.Css.SpriteClassName;
                    outIds.Add(newId);
                }

                response.Content += String.Format("\n\r{0} {{ background-position: {1}px 0px; }}", String.Join(", ", outIds), spritePart.StartsAt == 0 ? 0 : spritePart.StartsAt * -1);

            }

            base.MinifyContent(response);
        }

        public void SetHeaders(bool optimize, string[] fileNames)
        {
            base.SetHeaders(optimize);
            if (HttpContext.Current == null || !optimize)
                return;
            HttpContext.Current.Response.Cache.SetLastModifiedFromFileDependencies();
            HttpContext.Current.Response.AddFileDependencies(fileNames.ToArray());
        }

        public static Image Combine(string[] files)
        {
            //read all images into memory
            var images = new List<Image>();
            Image finalImage = null;

            try
            {
                var width = 0;
                var height = 0;

                foreach (var file in files)
                {
                    //create a Bitmap from the file and add it to the list
                    var bitmap = Image.FromFile(file);
                    //update the size of the final bitmap
                    width += bitmap.Width;
                    height = bitmap.Height > height ? bitmap.Height : height;
                    images.Add(bitmap);
                }

                //create a bitmap to hold the combined image
                finalImage = new Bitmap(width, height);

                //get a graphics object from the image so we can draw on it
                using (var g = Graphics.FromImage(finalImage))
                {
                    //set background color
                    //g.Clear(System.Drawing.Color.Black);

                    //go through each image and draw it on the final image
                    int offset = 0;
                    foreach (var image in images)
                    {
                        g.DrawImage(image, new Rectangle(offset, 0, image.Width, image.Height));
                        offset += image.Width;
                    }
                }

                return finalImage;
            }
            catch (Exception)
            {
                if (finalImage != null)
                    finalImage.Dispose();

                throw;
            }
            finally
            {
                //clean up memory
                foreach (var image in images)
                {
                    image.Dispose();
                }
            }
        }

        #endregion
    }
}